<?php
class Admin_Form implements Admin_Form_IEmbeddable, ArrayAccess
{
  protected
    $form,
    $name,
    $widgets,
    $validator;
  
  public function __construct($name = null)
  {
    $this->name    = $name;
    $this->widgets = array();
  }
  
  public function bind($values)
  {
    foreach ($this->widgets as $key => $widget)
    {
      if ($widget instanceof Admin_Form_Checkbox)
      {
        $this->widgets[$key]->setValue(isset($values[$key]) && $values[$key]);
      }
      elseif (isset($values[$key]))
      {
        $value = $values[$key];
        
        if (is_array($value) && $this->widgets[$key] instanceof Admin_Form)
        {
          $this->widgets[$key]->bind($value);
        }
        else
        {
          $this->widgets[$key]->setValue($value);
        }
      }
    }
  }
  
  public function clean($values)
  {
    if (is_callable($fn = $this->validator))
      return $fn($values);
    
    return $values;
  }
  
  public function toArray()
  {
    $values = array();
    foreach ($this->widgets as $name => $widget)
    {
      if ($widget instanceof self)
      {
        $values[$name] = $widget->toArray();
      }
      else
      {
        $values[$name] = $widget->getValue();
      }
    }
    return $values;
  }
  
  public function getNonceName()
  {
    return __CLASS__."_{$this->name}_Update";
  }
  
  public function nonce()
  {
    if (function_exists('wp_nonce_field'))
    {
      return wp_nonce_field($this->getNonceName(), '_wpnonce', true, false).
             input_hidden_tag("{$this->name}_Updated", 'Y');
    }
    
    return null;
  }
  
  public function getUpdatedInputName()
  {
    return "{$this->name}_Updated";
  }
  
  public function updated()
  {
    return isset($_POST[$this->getUpdatedInputName()]) &&
           ('Y' == $_POST[$this->getUpdatedInputName()]);
  }
  
  public function save($values)
  {
    check_admin_referer($this->getNonceName());
    $this->bind($this->clean($values));
    update_option($this->name, $this->toArray());
  }
  
  //-------------------------------------------------------------- IEMBEDDABLE -
  
  public function setParent($v)
  {
    $this->form = $v;
  }
  
  public function getParent()
  {
    return $this->form;
  }
  
  public function unsetParent()
  {
    $this->form = null;
  }
  
  //------------------------------------------------------- PROPERTY ACCESSORS -
  
  public function setName($v)
  {
    $this->name = $v;
  }
  
  public function getName()
  {
    if ($this->form)
      return $this->form->getName()."[{$this->name}]";
    else
      return $this->name;
  }
  
  //------------------------------------------------------------- ARRAY ACCESS -
  
  public function offsetExists($o)
  {
    return isset($this->widgets[$o]);
  }
  
  public function offsetUnset($o)
  {
    unset($this->widgets[$o]);
  }
  
  public function offsetSet($o, $v)
  {
    $v->setParent($this);
    $this->widgets[$o] = $v;
  }
  
  public function offsetGet($o)
  {
    return isset($this->widgets[$o]) ? $this->widgets[$o] : null;
  }
  
  //----------------------------------------------------------------------------
  
  public static function create($options)
  {
    $form = new Admin_Form($options['name']);
    
    if (isset($options['widgets']))
    {
      foreach ($options['widgets'] as $widgetName => $widgetConfig)
      {
        if (isset($widgetConfig['widgets']))
        {
          $form[$widgetName] = self::create(array_merge(
            $widgetConfig,
            array('name' => $widgetName)
            ));
        }
        elseif (isset($widgetConfig['type']))
        {
          $widgetConfig = array_merge(array(
            'options'      => null,
            'html_options' => null,
            ), $widgetConfig);

          $classname = $widgetConfig['type'];

          $form[$widgetName] = new $classname(
            $widgetName,
            $widgetConfig['options'],
            $widgetConfig['html_options']
            );
          if (isset($widgetConfig['default']))
            $form[$widgetName]->setValue($widgetConfig['default']);
        }
      }
    }
    
    if (isset($options['validator']) && is_callable($options['validator']))
      $this->validator = $options['validator'];
    
    return $form;
  }
}